#include "ScriptDomain.h"

using namespace System::IO;
using namespace System::Reflection;
using namespace System::Diagnostics;
using namespace System::Threading;
using namespace System::Runtime::CompilerServices;

namespace GTA
{
	ScriptDomain::ScriptDomain()
	{
		GTA::Log::Debug("ScriptDomain Constructor.");
		scriptmanager=nullptr;
		environmentBuilt=false;
		scriptDomainName = "GTAScript Domain";
		AppDomain::CurrentDomain->AssemblyResolve += gcnew ResolveEventHandler(domain_AssemblyResolver);
		AppDomain::CurrentDomain->UnhandledException += gcnew UnhandledExceptionEventHandler(domain_UnhandleException);
		Instance = this;
	}

	AppDomain^ ScriptDomain::GetScriptDomain()
	{ // STATIC
		return _scriptdomain;

	}

	AppDomain^ ScriptDomain::CreateScriptDomain()
	{
		GTA::Log::Debug("ScriptDomain CreateScriptDomain.");
		AppDomainSetup^ domaininfo = gcnew AppDomainSetup();
		domaininfo->ApplicationBase = AppDomain::CurrentDomain->BaseDirectory;

		System::Security::NamedPermissionSet^ nps = System::Security::Policy::PolicyLevel::CreateAppDomainLevel()->GetNamedPermissionSet("FullTrust");
		AppDomain^ scriptDomain = AppDomain::CreateDomain(scriptDomainName, nullptr, domaininfo, nps);
		_scriptdomain = scriptDomain;
		scriptDomain->InitializeLifetimeService();
		GTA::Log::Debug("ScriptDomain CreateScriptDomain success!");
		return scriptDomain;
	};

	ScriptManager^ ScriptDomain::CreateScriptManager(AppDomain^ scriptdomain)
	{
		String^ coreAsmLocation =  Assembly::GetExecutingAssembly()->Location;
		if (coreAsmLocation->Contains(".dll")) coreAsmLocation = coreAsmLocation->Replace(".dll",".asi"); // todo: do this properly
		String^ assemblyname = Assembly::GetExecutingAssembly()->FullName;
		String^ loadingtype = ScriptManager::typeid->FullName;
		Object^ o;
		try
		{
			o = scriptdomain->CreateInstanceAndUnwrap(assemblyname, loadingtype);
		}
		catch (Exception^ ex)
		{
			Log::Exception(ex);
		}
		try {
			scriptmanager = (ScriptManager^) o;
		} 
		catch (Exception^ ex) {
			GTA::Log::Exception(ex);
		}
		scriptmanager->Init(coreAsmLocation);
		scriptmanager->FindScriptAssemblies();
		environmentBuilt = true;
		return scriptmanager;
	}

	Assembly^ ScriptDomain::domain_AssemblyResolver( Object^ sender, ResolveEventArgs^ args )
	{
		try
		{
			Log::Debug("ScriptDomain AssemblyResolver");
			if(args->Name->Contains("GTAScript"))
			{
				return Assembly::GetExecutingAssembly();
			}
			else
			{
				Log::Error("Unable to retrieve AppDomain for: "+args->Name);
				return nullptr;
			}
		}
		catch (Exception^ ex)
		{
			Log::Exception(ex);
		}
		return nullptr;
	}

	void ScriptDomain::domain_UnhandleException(Object^ sender, UnhandledExceptionEventArgs^ e)
	{
		try
		{
			Log::Debug("ScriptDomain domain unhanded exception");
			Log::Exception(safe_cast<Exception^>(e->ExceptionObject));
			if ( e->IsTerminating )
			{
				Log::Error("Game crash!!!");
			}
		}
		catch (Exception^ ex)
		{
			Log::Exception(ex);
		}
	}
	

	void ScriptDomain::UnloadScriptDomain()
    {
		Log::Debug("ScriptDomain::UnloadScriptDomain()");
		if (scriptmanager != nullptr) 
		{
			scriptmanager->UnloadHooks();
			scriptmanager->UnloadAllScripts(); 
		}
        environmentBuilt = false;
		System::AppDomain::Unload(_scriptdomain);
        _scriptdomain = nullptr;
		Log::Debug("App domain unloaded!");
    }

	void ScriptDomain::ReloadScriptDomain()
    {
		Log::Debug("ScriptDomain::ReloadScriptDomain()");
        UnloadScriptDomain();
		CreateScriptDomain();
		CreateScriptManager(GetScriptDomain());
    }
};